package com.autumn.platform.dao.schema;

import static org.springframework.util.Assert.notNull;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.Configuration;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.util.xml.XmlValidationModeDetector;

import com.autumn.platform.core.xml.context.IXmlParserContext;
import com.autumn.platform.core.xml.context.IXmlParserContextFactory;
import com.autumn.platform.core.xml.context.impl.DefaultXmlParserContextFactory;
import com.autumn.platform.dao.SqlSessionFactoryBean;
import com.autumn.platform.dao.schema.parser.DefaultSchemaSqlMapperParser;
import com.autumn.platform.dao.schema.parser.ISchemaSqlMapperParser;

public class SchemaSqlSessionFactoryBean extends SqlSessionFactoryBean {

    private static final ISchemaSqlMapperParser DEFAULT_PARSER = new DefaultSchemaSqlMapperParser();

    private static final IXmlParserContextFactory DEFAULT_CONTEXT_FACTORY = new DefaultXmlParserContextFactory();

    private IXmlParserContextFactory contextFactory = DEFAULT_CONTEXT_FACTORY;

    private ISchemaSqlMapperParser parser = DEFAULT_PARSER;

    public SchemaSqlSessionFactoryBean() {
        super();
    }

    public IXmlParserContextFactory getContextFactory() {
        return contextFactory;
    }

    public void setContextFactory(IXmlParserContextFactory contextFactory) {
        if (null != contextFactory) {
            this.contextFactory = contextFactory;
        }
    }

    public ISchemaSqlMapperParser getParser() {
        return parser;
    }

    public void setParser(ISchemaSqlMapperParser parser) {
        if (null != parser) {
            this.parser = parser;
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        notNull(parser, "Property 'parser' is required");
        notNull(parser, "Property 'contextFactory' is required");
        super.afterPropertiesSet();
    }

    /**
     * 解析单个SqlMapper配置文件
     */
    @Override
    protected void doParseSqlMapperResource(Configuration configuration, Resource mapperLocation)
            throws NestedIOException {
        int mode = detectValidationMode(mapperLocation);
        if (mode == XmlValidationModeDetector.VALIDATION_DTD) {//如果是DTD，使用Mybatis官方的解析
            super.doParseSqlMapperResource(configuration, mapperLocation);
        } else {
            try {
                // 使用Schema校验
                this.doParseSqlMapperResourceWithSchema(configuration, mapperLocation);
            } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }

    /**
     * 解析使用XSD配置的SqlMapper文件
     * 
     * @param configuration
     * @param mapperLocation
     */
    protected void doParseSqlMapperResourceWithSchema(Configuration configuration, Resource mapperLocation) {
        IXmlParserContext context = this.getContextFactory().newContext();
        parser.parse(context, configuration, mapperLocation);
    }

    /**
     * 自定义设置
     */
    @Override
    protected void doCustomConfiguration(Configuration configuration) {
        super.doCustomConfiguration(configuration);
        /**
         * 设置默认的返回结果类型为Map
         */
        setDefaultResultType(configuration, Map.class);
    }

    private int detectValidationMode(Resource mapperLocation) throws NestedIOException {
        int mode = -1;
        try {
            XmlValidationModeDetector detector = new XmlValidationModeDetector();
            mode = detector.detectValidationMode(mapperLocation.getInputStream());
        } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
            ErrorContext.instance().reset();
        }
        return mode;
    }

    private void setDefaultResultType(Configuration configuration, Class<?> cls) {
        try {
            Field resultMaps = MappedStatement.class.getDeclaredField("resultMaps");
            resultMaps.setAccessible(true);
            for (Iterator<MappedStatement> i = configuration.getMappedStatements().iterator(); i.hasNext();) {
                Object mappedStatement = i.next();
                if (mappedStatement instanceof MappedStatement) {
                    MappedStatement ms = (MappedStatement) mappedStatement;
                    if (SqlCommandType.SELECT.equals(ms.getSqlCommandType()) && ms.getResultMaps().isEmpty()) {
                        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, ms.getId() + "-Inline", cls, new ArrayList<ResultMapping>(), null);
                        ResultMap resultMap = inlineResultMapBuilder.build();
                        List<ResultMap> rm = new ArrayList<ResultMap>();
                        rm.add(resultMap);
                        resultMaps.set(ms, Collections.unmodifiableList(rm));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
